package com.ls.knowledge.llm.service.impl;

import cn.hutool.core.util.StrUtil;
import com.ls.knowledge.llm.consts.ChatConsts;
import com.ls.knowledge.llm.dto.ChatMessageDto;
import com.ls.knowledge.llm.entity.ChatConversation;
import com.ls.knowledge.llm.entity.ChatConversationFile;
import com.ls.knowledge.llm.service.ChatConversationFileService;
import com.ls.knowledge.llm.service.ChatConversationService;
import com.ls.knowledge.llm.service.ChatService;
import com.ls.knowledge.llm.vo.MessageVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.util.*;


/**
 * @BelongsProject: knowledge
 * @BelongsPackage: com.ls.knowledge.llm.service
 * @Author: lishun
 * @CreateTime: 2025-06-17  22:14
 * @Description: 本地ollama大模型实现
 * @Version: 1.0
 */
@Slf4j
@Service
public class ChatServiceImpl implements ChatService {


    @Autowired
    private ChatConversationService chatConversationService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private ChatConversationFileService chatConversationFileService;


    @Value("${file.upload.path}")
    private String fileUploadPath;


    @Override
    public Flux<MessageVo> chatStream(ChatMessageDto chatMessageDto) {
        final String conversationId;

        if (StrUtil.isNotBlank(chatMessageDto.getConversationId())) {
            conversationId = chatMessageDto.getConversationId();
        } else {
            ChatConversation chatConversation = new ChatConversation();
            chatConversation.setTitle(chatMessageDto.getMessage());
            chatConversation.setCreateTime(LocalDateTime.now());
            chatConversation.setUpdateTime(LocalDateTime.now());
            chatConversationService.save(chatConversation);
            conversationId = chatConversation.getConversationId();
            chatMessageDto.setConversationId(conversationId);

        }
//        SearchRequest searchRequest = SearchRequest.builder()
//                .filterExpression(ChatConsts.CHAT_MESSAGE_LIST + " == " +finalConversationId )
//                .similarityThreshold(0.8d)
//                .topK(6)
//                .build();
//
//        QuestionAnswerAdvisor answerAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
//                .searchRequest(searchRequest)
//                .build();


        Flux<MessageVo> flux = chatClient
                .prompt(buildPrompt(chatMessageDto))
//                .advisors(answerAdvisor)
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
                .stream()
                .chatResponse()
                .map(item -> convertChatResponse(item, conversationId));
        // 创建开始的消息
        Flux<MessageVo> startFlux = Flux.just(
                MessageVo.builder()
                        .startFlag(true)
                        .conversationId(conversationId)
                        .build()
        );

        // 创建结束的消息
        Flux<MessageVo> endFlux = Flux.just(
                MessageVo.builder()
                        .endFlag(true)
                        .conversationId(conversationId)
                        .build());
        // 合并开始的消息，流的内容和结束的消息

        return startFlux.concatWith(flux)
                .concatWith(endFlux);
    }

    /**
     * 将模型的响应解析成java对象
     *
     * @param chatResponse
     * @param conversationId
     * @return
     */
    private MessageVo convertChatResponse(ChatResponse chatResponse, String conversationId) {
        ChatResponseMetadata metadata = chatResponse.getMetadata();
        // token使用信息
        Usage usage = metadata.getUsage();
        // 模型
        String model = metadata.getModel();

        Generation result = chatResponse.getResult();
        Map<String, Object> resultMedata = result.getOutput().getMetadata();
        // 候选回复？暂时没用到
        List<Generation> results = chatResponse.getResults();
        return MessageVo.builder()
                .conversationId(conversationId)
                .message(result.getOutput().getText())
                .build();

    }

    /**
     * 构建
     *
     * @param chatMessageDto
     * @return
     */
    private Prompt buildPrompt(ChatMessageDto chatMessageDto) {
        List<Message> messageList = new ArrayList<>();
        UserMessage userMessage = new UserMessage(chatMessageDto.getMessage());
        messageList.add(userMessage);

        Prompt prompt = new Prompt(messageList);
        return prompt;
    }

    @Override
    public String chat(ChatMessageDto dto) {
        return chatClient.prompt(buildPrompt(dto)).call()
                .content();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void uploadFileToConversation(MultipartFile file, String conversationId) {
        // 将文件存到本地路径中，记录访问路径
        try {
            Path uploadPath = Paths.get(fileUploadPath);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }
            // 使用原始文件名（注意：生产环境建议重命名防止冲突）
            String fileName = file.getOriginalFilename() + "_" + UUID.randomUUID();
            Path filePath = uploadPath.resolve(fileName);

            // 保存文件
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);

            ChatConversationFile conversationFile = new ChatConversationFile();
            conversationFile.setConversationUuid(conversationId);
            conversationFile.setName(fileName);
            conversationFile.setPath(filePath.toAbsolutePath().toString());
            chatConversationFileService.save(conversationFile);

            // 上传到知识库

            // 解析文件
            TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(file.getResource());
            List<Document> documents = tikaDocumentReader.get();

            // 切割文本

            TokenTextSplitter tokenTextSplitter = new TokenTextSplitter();
            List<Document> documentList = tokenTextSplitter.apply(documents);

            documentList.forEach(item -> {
                item.getMetadata().put(ChatConsts.CONVERSATION_ID, conversationId);
            });

            vectorStore.add(documentList);


        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}
